// SPDX-License-Identifier: GPL-2.0

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/printk.h>
#include <linux/fs.h>
#include <linux/security.h>
#include <linux/vmalloc.h>
#include <linux/mutex.h>
#include <crypto/hash.h>

#include "../common/dim_hash.h"
#include "../common/dim_pages.h"

static struct crypto_shash *tfm;
static struct shash_desc *desc;

static struct dentry *monitor_run_entry;
static struct dentry *monitor_status_entry;

static enum dim_hash_type hash_index = DIM_HASH_SHA256;

DEFINE_MUTEX(dim_monitor_mutex);

enum monitor_status_type {
	MONITOR_STATUS_READY = 0,
	MONITOR_STATUS_RUNNING,
	MONITOR_STATUS_ERROR,
	MONITOR_STATUS_PROTECTED,
	MONITOR_STATUS_MAX
};

#define MONITOR_OUTPUT_BASELINE_FILE	"/etc/dim/monitor_baseline"
#define MONITOR_STATUS_BUF_LEN	32

static char *status_array[MONITOR_STATUS_MAX] = {
	"ready",
	"running",
	"error",
	"protected"
};

static enum monitor_status_type cur_monitor_status;

extern unsigned long dim_get_baseline_pages(struct dim_pages_item **pages_array);
extern void dim_free_pages(struct dim_pages_item **pages_array);
extern struct dentry *dim_root;

static void free_global_data(void)
{
	if (desc != NULL) {
		vfree(desc);
		desc = NULL;
	}

	if (tfm != NULL) {
		crypto_free_shash(tfm);
		tfm = NULL;
	}

	if (monitor_run_entry != NULL)
		securityfs_remove(monitor_run_entry);

	if (monitor_status_entry != NULL)
		securityfs_remove(monitor_status_entry);
}

static ssize_t monitor_status(struct file *filp,
			      char __user *buf,
			      size_t count, loff_t *ppos)
{
	char tmpbuf[MONITOR_STATUS_BUF_LEN];
	ssize_t len;

	len = scnprintf(tmpbuf, MONITOR_STATUS_BUF_LEN, "%s\n",
		status_array[cur_monitor_status]);

	return simple_read_from_buffer(buf, count, ppos, tmpbuf, len);
}

static void monitor_write_to_file(char *buf, ssize_t size)
{
	struct file *fh = NULL;
	loff_t pos;
	ssize_t written;

	fh = filp_open(MONITOR_OUTPUT_BASELINE_FILE, O_CREAT | O_RDWR, S_IRUSR);
	if (IS_ERR(fh)) {
		printk(KERN_ALERT "DIM monitor create baseline file failed.");
		cur_monitor_status = MONITOR_STATUS_ERROR;
		return;
	}

	pos = fh->f_pos;
	written = kernel_write(fh, buf, size, &pos);
	if (written != size)
		printk(KERN_ALERT "DIM monitor write baseline file failed.");

	filp_close(fh, NULL);

	cur_monitor_status = MONITOR_STATUS_PROTECTED;
}

static int monitor_measure_run(void)
{
	struct dim_pages_item *pages_array = NULL;
	unsigned long counts;
	int i;
	int ret = 0;
	char digest_out[MAX_DIGEST_SIZE];

	if (!mutex_trylock(&dim_monitor_mutex))
		return -EBUSY;

	ret = crypto_shash_init(desc);
	if (ret < 0) {
		pr_err("hash init failed\n");
		goto out;
	}

	cur_monitor_status = MONITOR_STATUS_RUNNING;

	counts = dim_get_baseline_pages(&pages_array);
	if (counts == 0) {
		pr_warn("no baseline\n");
		cur_monitor_status = MONITOR_STATUS_ERROR;
		goto out;
	}

	for (i = 0; i < counts; i++)
		crypto_shash_update(desc, (char *)pages_array[i].pages_start,
			pages_array[i].size);

	ret = crypto_shash_final(desc, digest_out);
	if (ret < 0) {
		pr_err("shash final failed, %d\n", ret);
		cur_monitor_status = MONITOR_STATUS_ERROR;
		goto out;
	}

	monitor_write_to_file(digest_out, dim_hash_get_digest_len(hash_index));

out:
	dim_free_pages(&pages_array);
	mutex_unlock(&dim_monitor_mutex);
	return ret;
}

static ssize_t monitor_run(struct file *file,
				 const char __user *buf,
				 size_t count, loff_t *ppos)
{
	char *buf_copy = NULL;
	int val;
	int ret;

	if (*ppos != 0 || count > 2)
		return -EINVAL;

	buf_copy = memdup_user_nul(buf, count);
	if (IS_ERR(buf_copy))
		return PTR_ERR(buf_copy);

	if (sscanf(buf_copy, "%d", &val) != 1 || val != 1) {
		ret = -EINVAL;
		goto out;
	}

	pr_info("trigger DIM Monitor\n");

	ret = monitor_measure_run();
	if (ret < 0)
		goto out;

	ret = count;

out:
	kfree(buf_copy);
	return ret;
}

static const struct file_operations dim_monitor_run = {
	.write = monitor_run,
	.llseek = generic_file_llseek,
};

static const struct file_operations dim_monitor_status = {
	.read = monitor_status,
	.llseek = generic_file_llseek,
};

int dim_monitor_prepare(char *algo)
{
	struct tree_descr monitor_run = {"monitor_run", &dim_monitor_run, S_IRUSR|S_IWUSR};
	struct tree_descr monitor_status = {"monitor_status", &dim_monitor_status, S_IRUSR|S_IWUSR};
	int ret;

#if IS_MODULE(CONFIG_EULEROS_DIM_MONITOR)
	hash_index = dim_hash_get_index_by_name(algo);
#elif IS_BUILTIN(CONFIG_EULEROS_DIM_MONITOR)
	hash_index = dim_hash_get_cur_index();
#endif
	if (hash_index >= DIM_HASH_LAST)
		hash_index = DIM_HASH_SHA256;

	tfm = crypto_alloc_shash(dim_hash_get_name_by_index(hash_index), 0, 0);
	if (IS_ERR(tfm)) {
		ret = PTR_ERR(tfm);
		pr_err("hash algorithm init tfm failed, %d\n", ret);
		tfm = NULL;
		return ret;
	}

	desc = vmalloc(sizeof(struct shash_desc) + crypto_shash_descsize(tfm));
	if (desc == NULL) {
		ret = ENOMEM;
		pr_err("hash algorithm init desc failed, %d\n", ret);
		desc = NULL;
		goto out;
	}
	desc->tfm = tfm;

	monitor_run_entry = securityfs_create_file(monitor_run.name, monitor_run.mode,
								dim_root, NULL, monitor_run.ops);
	if (IS_ERR(monitor_run_entry)) {
		ret = PTR_ERR(monitor_run_entry);
		pr_err("monitor run interface create failed, %d\n", ret);
		monitor_run_entry = NULL;
		goto out;
	}
	monitor_status_entry = securityfs_create_file(monitor_status.name, monitor_status.mode,
								dim_root, NULL, monitor_status.ops);
	if (IS_ERR(monitor_status_entry)) {
		ret = PTR_ERR(monitor_status_entry);
		pr_err("monitor status interface create failed, %d\n", ret);
		monitor_status_entry = NULL;
		goto out;
	}

	cur_monitor_status = MONITOR_STATUS_READY;

	return 0;
out:
	free_global_data();
	return ret;
}

void dim_monitor_finish(void)
{
	cur_monitor_status = MONITOR_STATUS_READY;

	free_global_data();
}
